/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using UnitedBinary.UnitTests.Framework;
using System.Collections.Generic;

namespace UnitedBinary.HarnessIt.SharedTypes
{
	/// <summary>
	///    The class "Class" represents a .NET class which likely contains unit tests.
	/// </summary>
	[Serializable]
	public class Class : IComparable, IClass
	{
		#region Member Variables

		private Type myType;
		private bool active = true;
		private Assembly myAssembly;
		private bool isTest;
		private string name;
		private string fullName;
		private string description = "";
		private int requiredTestCount;
		private int totalMethodCount;
		private Method[] methods;
		private bool isOrdered;
		private int subOrderMode = 100;
		private TestOrderModes orderMode = TestOrderModes.Neutral;
		private TestExecutionModes executionMode = TestExecutionModes.Renew;

		#endregion

		#region Construction and Initialization

		/// <summary>
		///		Initializing constructor.
		/// </summary>
		/// <param name="type">System.Type value which represents the class information in Relfection form.</param>
		public Class(Type type, Assembly containingAssembly)
		{
			if ( type == null )
			{
				throw new ArgumentNullException( "type", "Type cannot be null." );
			}
			else if ( containingAssembly == null )
			{
				throw new ArgumentNullException( "containingAssembly", "MyAssembly cannot be null." );
			}

			myType = type;
			myAssembly = containingAssembly;
			name = myType.Name;
			fullName = myType.FullName;
			List<Method> methodsList = new List<Method>();

			try
			{
				Attribute[] attributes = Attribute.GetCustomAttributes( myType, true );
				foreach ( Attribute attribute in attributes )
				{
					TestExecutionModeAttribute tem = attribute as TestExecutionModeAttribute;
					if ( tem != null )
					{
						executionMode = tem.Mode;
						continue;
					}
					TestClassAttribute tca = attribute as TestClassAttribute;
					isTest = isTest || tca != null;
					if ( tca != null )
					{
						description = tca.Description;
						continue;
					}
					TestOrderAttribute toa = attribute as TestOrderAttribute;
					isOrdered = toa != null;
					if ( isOrdered )
					{
						orderMode = toa.Mode;
						subOrderMode = toa.SubModeOrder;
						continue;
					}
				}

				MethodInfo[] foundMethods = myType.GetMethods( BindingFlags.Public | BindingFlags.Instance );
				totalMethodCount = foundMethods.Length;

				foreach ( MethodInfo m in foundMethods )
				{
					attributes = Attribute.GetCustomAttributes( m, true );
					foreach ( Attribute attribute in attributes )
					{
						TestMethodAttribute tma = attribute as TestMethodAttribute;
						if ( tma != null )
						{
							isTest = true;
							Method method = new Method( m, this, tma );
							// collect method statistics here...
							methodsList.Add( method );
							break;
						}
					}
				}
			}
			catch ( FileLoadException e )
			{
				throw new InconsistentTestBasisException( "Inconsistent Test Basis Assembly.", e );
			}
			methodsList.Sort();
			this.methods = methodsList.ToArray();
		}

		#endregion

		#region Unit Test Related Code

		public TestExecutionModes ExecutionMode
		{
			get { return executionMode; }
		}

		/// <summary>
		///		Description is the text description entered when using the TestClass
		///		custom attribute from the unit testing framework. Is null if there is
		///		no such attribute.
		/// </summary>
		public string Description
		{
			get { return description; }
		}

		public int TestCount
		{
			get { return methods.Length; }
		}

		/// <summary>
		///		Indicates whether this class has the UBTestClass attribute.
		/// </summary>
		public bool IsTest
		{
			get { return isTest; }
		}

		public TestOrderModes OrderMode
		{
			get { return orderMode; }
		}

		public int SubModeOrder
		{
			get { return subOrderMode; }
		}

		public bool IsOrdered
		{
			get { return isOrdered; }
		}

		/// <summary>
		///		MethodCount is the number of methods - regular and unit test methods.
		/// </summary>
		public int MethodCount
		{
			get { return totalMethodCount; }
		}

		/// <summary>
		///		Methods is an array of Method objects representing the methods of this class.
		/// </summary>
		public Method[] Methods
		{
			get { return methods; }
		}

		/// <summary>
		///		Name is the name of this class.
		/// </summary>
		public string Name
		{
			get { return name; }
		}

		public string FullName
		{
			get { return fullName; }
		}

		public Type Type
		{
			get { return myType; }
		}

		public Assembly ParentAssembly
		{
			get { return myAssembly; }
		}

		public bool Active
		{
			get { return active; }
			set { active = value; }
		}

		public bool ContainsActiveTests
		{
			get
			{
				foreach ( Method m in methods )
				{
					if ( m.Active )
					{
						return true;
					}
				}
				return false;
			}
		}

		internal int RequiredTestCount
		{
			get { return requiredTestCount; }
			set
			{
				myAssembly.RequiredTestCount += (value - requiredTestCount);
				requiredTestCount = value;
			}
		}

		public bool ContainsRequiredTests
		{
			get { return requiredTestCount > 0; }
		}

		public bool ContainsOnlyRequiredTests
		{
			get { return ContainsRequiredTests && (requiredTestCount == methods.Length); }
		}

		public bool ContainsTests
		{
			get { return methods.Length > 0; }
		}

		/// <summary>
		///		TestRecord is the cummulative TestMethodRecord for all the methods in this class.
		/// </summary>
		public TestMethodRecord TestRecord
		{
			get
			{
				TestMethodRecord tmr = new TestMethodRecord();
				foreach ( Method m in methods )
				{
					tmr += m.TestRecord;
				}
				return tmr;
			}
		}

		/// <summary>
		///		ResetTests resets the test results for all the methods in this class.
		/// </summary>
		public void ResetTests()
		{
			foreach ( Method m in methods )
			{
				m.ResetTest();
			}
		}

		public double RunningTime
		{
			get
			{
				double dt = 0.0;
				foreach ( Method m in methods )
				{
					dt += m.RunningTime;
				}
				return dt;
			}
		}

		#endregion

		#region IComparable Implementation

		public int CompareTo(object obj)
		{
			Class c = obj as Class;

			if ( c == null )
			{
				throw new ArgumentException( "Could not compare objects. Invalid object type or object null." );
			}

			// If they have the same order mode, we must compare the 
			// submode order to decide which should come first.
			if ( this.OrderMode == c.OrderMode )
			{
				if ( this.SubModeOrder == c.SubModeOrder )
				{
					return 0;
				}
				else if ( this.SubModeOrder < c.SubModeOrder )
				{
					return -1;
				}
				return 1;
			}

			// If we are here, ordermodes are not equal and
			// this class's order mode is first (so c's isn't first).
			// This class should come first.
			if ( this.OrderMode == TestOrderModes.First )
			{
				return -1;
			}

			// If we are here, ordermodes are not equal and
			// this class's order mode is last (so c's isn't last).
			// m should come first.
			if ( this.OrderMode == TestOrderModes.Last )
			{
				return 1;
			}

			// This class's order mode isn't first or last
			// so we must determine whether c's order mode
			// is first or last (those are the only possiblities
			// left.
			if ( c.OrderMode == TestOrderModes.First )
			{
				return 1;
			}

			if ( c.OrderMode == TestOrderModes.Last )
			{
				return -1;
			}

			return 0;
		}

		#endregion

		#region IClass Members

		IMethod[] IClass.Methods
		{
			get { return Methods; }
		}

		IAssembly IClass.ParentAssembly
		{
			get { return this.ParentAssembly; }
		}


		#endregion
	}
}